#pragma once

#include <ctime>
#include <istream>
#include <sstream>
#include <unordered_map>
#include <stdexcept>
#include <functional>
#include "rpc_transport.h"
#include "rpc_logger.h"
#include "rpc_singleton.h"
#include "rpc_service.h"
#include "coroutine/coroutine.h"
#include "rpc_proxy.h"
#include "rpc_stub.h"

namespace kratos {
namespace service {
    class ServiceContext;
}
}

namespace rpc {

// RPC framework
class Rpc {
    bool coroMode_ {true}; ///< Start coroutine for each service implementation
    void* context_ {nullptr}; ///< User context pointer

public:
    // Initialize
    // @param logger external @see Logger interface pointer
    // @param coroMode Start coroutine for each service implementation
    void initialize(Logger* logger = nullptr, bool coroMode = true);
    // Call this method in your main loop
    void update();
    // Call it when received some RPC data
    // @param transport User implemented Transport interface
    void onMessage(TransportPtr transport, kratos::service::ServiceContext* ctx = nullptr);
    // Returns @see Logger interface pointer
    Logger* getLogger();
    // Set user context pointer
    void setContext(void* context);
    // Get user context pointer
    template <typename T>
    T* getContext() {
        return reinterpret_cast<T*>(context_);
    }

private:
    // Call a stub service
    // @param header message header
    // @param transport message stream
    void onCall(const RpcMsgHeader& header, TransportPtr transport, kratos::service::ServiceContext* ctx = nullptr);
    // Call a proxy to receive returning message
    // @param header message header
    // @param transport message stream
    void onReturn(const RpcMsgHeader& header, TransportPtr transport);
    // Build header from stream
    // @param transport message stream
    // @param [OUT] header message header
    // @retval true success
    // @retval false failure
    bool readHeader(TransportPtr transport, RpcMsgHeader& header);
    // Service not found and return message
    // @param transport message stream
    // @param callID proxy call ID
    void notFound(TransportPtr transport, CallID callID);
    // Process message
    // @param header message header
    // @param transport message stream    
    void doMessage(const RpcMsgHeader& header, TransportPtr transport, kratos::service::ServiceContext* ctx = nullptr);
    // Find service
    // @param serviceID service instance ID
    // @param uuid service UUID
    StubPtr getService(ServiceID serviceID, ServiceUUID uuid, kratos::service::ServiceContext* ctx = nullptr);
    // Is stub running in a coroutine
    // @retval true yes
    // @retval false no
    bool isCoroMode() { return coroMode_; }

    friend class Stub;
    friend class StubCall;
    friend class TransportGuard;
};

// Initialize RPC framework
// @param logger external logger pointer
// @param coroMode true means processing method call in coroutine
void initialize(Logger* logger = nullptr, bool coroMode = true);

// Destory RPC framework
void deinitialize();

// Call this method in your main loop
void update();

// Call it when received some RPC data
// @param transport User implemented Transport interface
void onMessage(TransportPtr transport, kratos::service::ServiceContext* ctx = nullptr);

// Load a bundle
// @param uuid Service UUID
// @param bundlePath bundle path
// @param ctx context
// @retval true success
// @retval false failure
bool loadClass(const std::string& uuid, const std::string& bundlePath, kratos::service::ServiceContext* ctx = nullptr);

// Unload all bundles
void unloadClassUnsafe(const std::string& uuid);

#define RpcRef (*rpc::Singleton<rpc::Rpc>::instance())

}
